package com.wangshouyu.mp.pay.common.utils;

import com.google.common.base.Strings;
import com.wangshouyu.mp.pay.common.constant.Constant;
import com.wangshouyu.mp.pay.common.enums.SignType;
import com.wangshouyu.mp.pay.common.exception.SignException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.Signature;
import java.util.*;

/**
 * @author 王守钰
 * @description 签名工具
 * @date 2020/8/13 9:46
 */
public class SignUtils {

    private static Logger log = LoggerFactory.getLogger(SignUtils.class);

    private SignUtils(){}

    private static final String UTF8 = "utf-8";


    /**
     * 微信v3支付接口token
     * @param method 请求方法
     * @param url 请求地址
     * @param body 请求主体
     * @param mchId 商户id
     * @param mchSerialNo 商户证书序列号
     * @param privateKey 私钥
     * @return 支付token
     */
    public static String v3PayToken(String method,
                                    String url,
                                    String body,
                                    String mchId,
                                    String mchSerialNo,
                                    PrivateKey privateKey) {
        try{
            String nonceStr = UUID.randomUUID().toString().replaceAll("-", "");
            // 时间
            long timestamp = System.currentTimeMillis() / 1000;
            // 消息
            StringBuffer message = new StringBuffer();
            // 构建消息
            message
                    .append(method).append("\n")
                    .append(url).append("\n")
                    .append(timestamp).append("\n")
                    .append(nonceStr).append("\n")
                    .append(body).append("\n");

            // 获取签名
            String signature = v3PayToken(message.toString(), privateKey);
            // 构建token
            StringBuffer token = new StringBuffer();
            token
                    .append("mchid=\"").append(mchId).append("\",")
                    .append("nonce_str=\"").append(nonceStr).append("\",")
                    .append("timestamp=\"").append(timestamp).append("\",")
                    .append("serial_no=\"").append(mchSerialNo).append("\",")
                    .append("signature=\"").append(signature).append("\"");

            return token.toString();
        }catch (Exception e){
            throw new SignException("构建v3微信支付Token异常", e);
        }

    }

    /**
     * 微信v3支付接口token
     * @param message 加密消息
     * @param privateKey 私钥
     * @return 支付token
     */
    public static String v3PayToken(String message,
                                    PrivateKey privateKey) {
        try{

            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initSign(privateKey);
            sign.update(message.getBytes(UTF8));
            // 获取签名
            return Base64.getEncoder().encodeToString(sign.sign());

        }catch (Exception e){
            throw new SignException("构建v3微信支付Token异常", e);
        }

    }



    /**
     * 生成签名，默认MD5
     * @param data 待签名数据
     * @param key API密钥
     * @return 签名
     */
    public static String sign(final Object data, String key) {
        return md5Sign(data, key);
    }

    /**
     * MD5生成签名
     * @param data 待签名数据
     * @param key API密钥
     * @return 签名
     */
    public static String md5Sign(final Object data, String key) {
        return sign(data, key, SignType.MD5);
    }

    /**
     * hmacSha256生成签名
     * @param data 待签名数据
     * @param key API密钥
     * @return 签名
     */
    public static String hmacSha256Sign(final Object data, String key) {
        return sign(data, key, SignType.HMAC_SHA256);
    }

    /**
     * 生成签名. 注意，若含有sign_type字段，必须和signType参数保持一致。
     * @param data 待签名数据
     * @param key API密钥
     * @param signType 签名方式
     * @return 签名
     */
    public static String sign(Object data, String key, SignType signType) {
        Map<String, String> map = XmlUtils.xml2Map(XmlUtils.object2Xml(data));
        log.info("sign map:{}", map);
        Set<String> keySet = map.keySet();
        String[] keyArray = keySet.toArray(new String[keySet.size()]);
        Arrays.sort(keyArray);
        StringBuffer sb = new StringBuffer();
        String val = null;
        for (String k : keyArray) {
            if (k.equals(Constant.FIELD_SIGN)) {
                continue;
            }
            // 参数值为空，则不参与签名
            if (!Strings.isNullOrEmpty(val = map.get(k))) {
                sb
                        .append(k)
                        .append("=")
                        .append(val)
                        .append("&");
            }
        }
        if(!Strings.isNullOrEmpty(key)){
            sb.append("key=").append(key);
        }
        try{
            if (null == signType || SignType.MD5.equals(signType)) {
                return md5(sb.toString()).toUpperCase();
            } else if (SignType.HMAC_SHA256.equals(signType)) {
                return hmacSha256(sb.toString(), key);
            } else {
                throw new SignException(String.format("Invalid sign_type: %s", signType));
            }

        }catch (Exception e){
            throw new SignException(e);
        }
    }

    /**
     * 生成 MD5
     *
     * @param data 待处理数据
     * @return MD5结果
     */
    private static String md5(String data) throws Exception {
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] array = md.digest(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }

    /**
     * 生成 HMAC-SHA256
     * @param data 待处理数据
     * @param key 密钥
     * @return 加密结果
     * @throws Exception
     */
    private static String hmacSha256(String data, String key) throws Exception {
        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
        sha256_HMAC.init(secret_key);
        byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }
}
